/*
 * ENUM Discoverer. ENUM Demonstration software, demonstrating the potential of ENUM.
 * 
 * Copyright (C) 2009 SIDN and ISOC.nl
 * 
 * This file is part of ENUM Discoverer.
 *
 * ENUM Discoverer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ENUM Discoverer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ENUM Discoverer.  If not, see <http://www.gnu.org/licenses/>.
 */

package nl.sidn.enumdiscoverer.contacts.adapters;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import nl.sidn.android.syncml.pim.PropertyNode;
import nl.sidn.android.syncml.pim.VDataBuilder;
import nl.sidn.android.syncml.pim.VNode;
import nl.sidn.android.syncml.pim.vcard.VCardException;
import nl.sidn.android.syncml.pim.vcard.VCardParser;
import nl.sidn.enumdiscoverer.EnumRecord;
import nl.sidn.enumdiscoverer.LogConfig;
import nl.sidn.enumdiscoverer.Utils;
import nl.sidn.enumdiscoverer.contacts.ContactProfileAggregator;

import android.text.TextUtils;
import android.util.Log;

/**
 * Looks up vCard using a URL retrieved from ENUM and stores useful fields to Contacts.
 * @author	Grant Patterson (grant.patterson@catalyst.net.nz)
 */
public class VCardAdapter implements ContactAdapter {
	
	static final String TAG = LogConfig.getLogTag(VCardAdapter.class);
	// whether DEBUG level logging is enabled (whether globally, or explicitly for this log tag)
	static final boolean DEBUG = LogConfig.isDebug(TAG);
	// whether VERBOSE level logging is enabled
	static final boolean VERBOSE = LogConfig.VERBOSE;
	
	private static final int BUF_SIZE = 4096;

	@Override
	public void updateContact(String urlCapture, EnumRecord enumRecord,
			ContactProfileAggregator profileAggregator) {
		
		// Fetch vCard and parse
		
		InputStream is = Utils.fetchFromUrl(enumRecord.getUrl());
		if (is == null) {
			Log.w(TAG, "Failed to fetch vCard " + enumRecord.getUrl());
			return;
		}
		
		VNode vCard = parseVCard(is);
		if (vCard == null) {
			return;
		}
		
		ArrayList<PropertyNode> props = vCard.propList;
		int size = props.size();
		for (int i = 0; i < size; i++) {

			PropertyNode prop = props.get(i);

			if        (   "FN".equals(prop.propName)) {
				profileAggregator.addName(prop.propValue_vector.get(0), enumRecord);
			} else if ("PHOTO".equals(prop.propName)) {
				profileAggregator.addPhoto(prop.propValue_vector.get(0), enumRecord);
			} else if (  "ADR".equals(prop.propName)) {
				processAddress(prop, enumRecord, profileAggregator);
			} else if (  "ORG".equals(prop.propName)) {
				profileAggregator.addOrganisation(prop.propValue_vector.get(0), enumRecord);
			} else if ( "NOTE".equals(prop.propName)) {
				profileAggregator.addStatus(prop.propValue_vector.get(0), enumRecord);
			}
		}
	}
	
	private void processAddress(PropertyNode prop, EnumRecord enumRecord,
			ContactProfileAggregator profileAggregator) {
		
		// The ADR property is structured as:
		// (PO box);(extended address);(street address);(locality);(region);(postal code);(country)
		
		List<String> values = prop.propValue_vector;
		if ( values.size() != 7 ) {
			Log.w(TAG, "Error parsing vCard, badly formed ADR property structure");
			return;
		}
 
		String	poBox = values.get(0);
		String	extAddr = values.get(1);
		String	streetAddr = values.get(2);
		String	locality = values.get(3);
		String	region = values.get(4);
		String	postCode = values.get(5);
		String	country = values.get(6);
		
		// The address string we will store in contacts
		String address = "";
		
		if (!TextUtils.isEmpty(streetAddr)) {
			if (!TextUtils.isEmpty(extAddr)) {
				address += extAddr + "\n";
			} 
			address += streetAddr + "\n";
		} else {
			address += poBox + "\n";
		}
		
		if (!TextUtils.isEmpty(locality)) {
			address += locality;
			if (!TextUtils.isEmpty(postCode)) {
				address += " " + postCode;
			}
			address += "\n";
		} else if (!TextUtils.isEmpty(postCode)) {
			address += postCode;
		}
		
		if (!TextUtils.isEmpty(region)) address += region + "\n";
		address += country;
		
		profileAggregator.addAddress(address, enumRecord);
	}
	
	private VNode parseVCard(InputStream is) {
		
		VCardParser parser = new VCardParser();
		VDataBuilder builder = new VDataBuilder();
		
		try {
			String vcardString = readStreamToString(is);
	
			//parse the string
			boolean parsed = parser.parse(vcardString, builder);
			if (!parsed) {
			    Log.w(TAG, "Failed to parse vCard");
			    return null;
			}
		
		} catch (IOException e) {
			Log.w(TAG, "Error parsing vCard", e);
			return null;
		} catch (VCardException e) {
			Log.w(TAG, "Error parsing vCard", e);
			return null;
		}

		// we expect one vCard record in stream
		List<VNode> vCardRecords = builder.vNodeList;
		if (vCardRecords.size() == 1) {
			return vCardRecords.get(0);
		} else if (vCardRecords.size() == 0) {
			Log.d("TAG", "vCard contains no records");
			return null;
		} else { // size > 1
			Log.d(TAG, "vCard contains multiple records. Ignoring all but the first");
			return vCardRecords.get(0);
		}
	}
	
	private String readStreamToString(InputStream is) throws IOException {
		
		InputStreamReader isr = new InputStreamReader(is);
		
		try {
			
			StringBuilder sb = new StringBuilder();
			
			char[] buf = new char[BUF_SIZE];
			int len;
			
			while ((len = isr.read(buf)) != -1) {
				sb.append(buf, 0, len);
			}
			
			return sb.toString();
			
		} finally {
			isr.close();
		}
	}

	@Override
	public String toString() {
		return getClass().getSimpleName();
	}
}
